// Copyright (c) Aptos Foundation
// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE

use crate::{
    move_vm_ext::{AptosMoveResolver, SessionExt},
    verifier::{transaction_arg_validation, transaction_arg_validation::get_allowed_structs},
};
use aptos_types::vm::module_metadata::RuntimeModuleMetadataV1;
use move_binary_format::errors::{PartialVMError, PartialVMResult};
use move_core_types::{identifier::IdentStr, vm_status::StatusCode};
use move_vm_runtime::{
    module_traversal::{TraversalContext, TraversalStorage},
    LoadedFunction, Loader,
};
use move_vm_types::gas::{GasMeter, UnmeteredGasMeter};

/// Based on the function attributes in the module metadata, determine whether a
/// function is a view function.
pub fn determine_is_view(
    module_metadata: Option<&RuntimeModuleMetadataV1>,
    fun_name: &IdentStr,
) -> bool {
    if let Some(data) = module_metadata {
        data.fun_attributes
            .get(fun_name.as_str())
            .map(|attrs| attrs.iter().any(|attr| attr.is_view_function()))
            .unwrap_or_default()
    } else {
        false
    }
}

/// Validate view function call. This checks whether the function is marked as a view
/// function, and validates the arguments.
pub(crate) fn validate_view_function(
    session: &mut SessionExt<impl AptosMoveResolver>,
    loader: &impl Loader,
    gas_meter: &mut impl GasMeter,
    traversal_context: &mut TraversalContext,
    args: Vec<Vec<u8>>,
    fun_name: &IdentStr,
    func: &LoadedFunction,
    module_metadata: Option<&RuntimeModuleMetadataV1>,
    struct_constructors_feature: bool,
) -> PartialVMResult<Vec<Vec<u8>>> {
    // Must be marked as view function.
    let is_view = determine_is_view(module_metadata, fun_name);
    if !is_view {
        return Err(
            PartialVMError::new(StatusCode::INVALID_MAIN_FUNCTION_SIGNATURE)
                .with_message("function not marked as view function".to_string()),
        );
    }

    // Must return values.
    if func.return_tys().is_empty() {
        return Err(
            PartialVMError::new(StatusCode::INVALID_MAIN_FUNCTION_SIGNATURE)
                .with_message("view function must return values".to_string()),
        );
    }

    let allowed_structs = get_allowed_structs(struct_constructors_feature);
    let result = if loader.is_lazy_loading_enabled() {
        transaction_arg_validation::construct_args(
            session,
            loader,
            gas_meter,
            traversal_context,
            func.param_tys(),
            args,
            func.ty_args(),
            allowed_structs,
            true,
        )
    } else {
        let traversal_storage = TraversalStorage::new();
        transaction_arg_validation::construct_args(
            session,
            loader,
            // No metering with eager loading.
            &mut UnmeteredGasMeter,
            &mut TraversalContext::new(&traversal_storage),
            func.param_tys(),
            args,
            func.ty_args(),
            allowed_structs,
            true,
        )
    };
    result.map_err(|e| PartialVMError::new(e.status_code()))
}
